package com.codename1.ui;

import com.codename1.cloud.BindTarget;
import com.codename1.junit.UITestBase;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.FocusListener;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.Style;
import org.junit.jupiter.api.Test;

import java.util.HashSet;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Additional test coverage for Component methods that were previously untested.
 * These tests properly trigger methods through public APIs and component lifecycle.
 *
 * METHODS NOT COVERED (with reasoning):
 *
 * 1. EDT/Event Dispatch Thread Methods - Require Display initialization and EDT:
 *    - fireFocusLost(Component) - Fires events via Display.callSerially
 *    - fireFocusLost() - Fires events via Display.callSerially
 *    - fireClicked() - Fires events via Display.callSerially
 *    - longPointerPress(int, int) - Fires long press events via Display.callSerially
 *    - pointerReleaseMaterialPullToRefresh() - Material design pull-to-refresh EDT handling
 *    - updateMaterialPullToRefresh(Form, int) - Material design pull-to-refresh EDT handling
 *
 * 2. Protected/Package-Private Internal Implementation Methods:
 *    - dragFinishedImpl(Component, int, int) - Protected internal drag implementation
 *    - dragFinishedImpl(int, int) - Protected internal drag implementation
 *    - focusLostInternal() - Protected internal focus handling
 *    - startTensile(int, int, boolean) - Protected tensile drag implementation
 *    - initScrollMotion() - Protected scroll motion initialization
 *    - paintPullToRefresh(Graphics) - Protected painting method
 *    - installDefaultPainter(Style) - Protected painter installation
 *    - deinitializeCustomStyle(Style) - Protected style deinitialization
 *    - stopComponentLableTicker() - Protected ticker management
 *
 * 3. Private Methods - Not accessible from tests:
 *    - getInlineUnselectedStyleStrings() - Private style string getter
 *    - getInlineSelectedStyleStrings() - Private style string getter
 *    - getInlinePressedStyleStrings() - Private style string getter
 *    - getInlineDisabledStyleStrings() - Private style string getter
 *    - getInlineStylesUIID() - Private inline styles UIID
 *    - getInlineStylesUIID(String) - Private inline styles UIID
 *    - findNegativeScrolls(Set) - Private scroll finding
 *    - chooseScrollXOrY(int, int) - Private scroll direction chooser
 *    - scrollableYFlag() - Private scrollable flag
 *    - getScrollableFast() - Private fast scrollable check
 *    - isHidden(boolean) - Private hidden state check
 *    - distance(int[], int[]) - Private utility method
 *    - drawShadow(Graphics, Image, int, int, int, int, int, int, int, float) - Private shadow drawing
 *
 * 4. Methods Requiring Complex Platform/Native Setup:
 *    - getNativeOverlay() - Requires native platform overlay support
 *    - useNativeShadowRendering() - Platform-specific shadow rendering flag
 *    - getTextSelectionSupport() - Requires text selection infrastructure
 *    - dp2px(int) - Requires Display density metrics (tested indirectly via other methods)
 *
 * 5. Methods Requiring Specific Component State/Hierarchy:
 *    - findDropTarget(Component, int, int) - Requires drag/drop hierarchy setup
 *    - getLeadComponent() - Requires lead component pattern setup
 *    - createStyleAnimation(String, int) - Requires animation infrastructure
 *    - setAnimationMotion(Motion) - Requires motion animation setup
 *    - setInlineStylesTheme(Resources) - Requires resource theme setup
 *
 * 6. Property Binding Methods - Tested via testBoundPropertyValues():
 *    - getBoundPropertyValue(String) - Returns null on base Component (tested)
 *    - setBoundPropertyValue(String, Object) - No-op on base Component (tested)
 *
 * Total: 40 methods cannot be directly covered due to:
 * - EDT/Event firing requirements (6 methods)
 * - Protected/private access (22 methods)
 * - Platform-specific requirements (4 methods)
 * - Complex hierarchy/setup requirements (5 methods)
 * - Property binding tested separately (2 methods, covered via testBoundPropertyValues)
 *
 * These methods are either:
 * - Tested indirectly through higher-level public APIs
 * - Protected/private implementation details
 * - Require complex setup that would make tests brittle
 * - Platform-specific functionality not available in test environment
 */
class ComponentCoverageTest extends UITestBase {

    // ========== Shadow Rendering Tests ==========

    @Test
    void testPaintShadowsWithElevation() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(50);
        component.setX(10);
        component.setY(20);

        // Set elevation to trigger shadow painting
        component.getAllStyles().setElevation(5);
        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - not needed for testing paintShadows

        Image canvas = Image.createImage(200, 200, 0xFFFFFF);
        Graphics g = canvas.getGraphics();

        // paintShadows is public, can call directly
        assertDoesNotThrow(() -> component.paintShadows(g, 10, 10));
    }

    @Test
    void testPaintShadowsWithZeroElevation() {
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(50);
        component.getAllStyles().setElevation(0);

        Image canvas = Image.createImage(200, 200, 0xFFFFFF);
        Graphics g = canvas.getGraphics();

        // Should not throw even with zero elevation
        assertDoesNotThrow(() -> component.paintShadows(g, 10, 10));
    }

    @Test
    void testComponentWithElevationRendering() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(100);

        form.add(BorderLayout.CENTER, component);

        // Setting elevation on the unselected style (base style)
        Style style = component.getUnselectedStyle();
        style.setElevation(10);
        assertEquals(10, style.getElevation());

        // Test that paintShadows can be called (used during rendering with elevation)
        Image img = Image.createImage(200, 200, 0xFFFFFF);
        Graphics g = img.getGraphics();
        assertDoesNotThrow(() -> component.paintShadows(g, 0, 0));
    }

    // ========== Drag and Drop Tests ==========

    @Test
    void testDragFinishedProtectedMethod() {
        TestableComponent component = new TestableComponent();
        component.setDraggable(true);

        // dragFinished is protected, can override and call
        assertDoesNotThrow(() -> component.dragFinished(10, 20));
        assertTrue(component.dragFinishedCalled, "dragFinished should have been called");
    }

    @Test
    void testDropTargetBehavior() {
        Form form = new Form(new BorderLayout());
        TestableComponent source = new TestableComponent();
        TestableComponent target = new TestableComponent();

        source.setDraggable(true);
        target.setDropTarget(true);

        Container container = new Container(new BoxLayout(BoxLayout.Y_AXIS));
        container.add(source);
        container.add(target);
        form.add(BorderLayout.CENTER, container);
        // Don't call form.show() - not needed to test drag/drop properties

        assertTrue(target.isDropTarget());
        assertTrue(source.isDraggable());
    }

    @Test
    void testDragAndDropListeners() {
        TestableComponent component = new TestableComponent();
        component.setDraggable(true);
        component.setDropTarget(true);

        final boolean[] dropCalled = {false};
        final boolean[] dragOverCalled = {false};
        final boolean[] dragFinishedCalled = {false};

        ActionListener dropListener = evt -> dropCalled[0] = true;
        ActionListener dragOverListener = evt -> dragOverCalled[0] = true;
        ActionListener dragFinishedListener = evt -> dragFinishedCalled[0] = true;

        component.addDropListener(dropListener);
        component.addDragOverListener(dragOverListener);
        component.addDragFinishedListener(dragFinishedListener);

        // Verify listeners were added (they won't be null internally)
        component.removeDropListener(dropListener);
        component.removeDragOverListener(dragOverListener);
        component.removeDragFinishedListener(dragFinishedListener);
    }

    @Test
    void testDragEnterAndExitFlow() {
        TestableComponent source = new TestableComponent();
        TestableComponent target = new TestableComponent();

        source.setDraggable(true);
        target.setDropTarget(true);

        // These are public methods for drag/drop protocol
        assertDoesNotThrow(() -> source.dragEnter(target));
        assertDoesNotThrow(() -> source.dragExit(target));
    }

    @Test
    void testDropOperation() {
        TestableComponent component = new TestableComponent();
        TestableComponent target = new TestableComponent();
        component.setDraggable(true);
        target.setDropTarget(true);

        assertDoesNotThrow(() -> component.drop(target, 10, 20));
    }

    @Test
    void testDragInitiated() {
        TestableComponent component = new TestableComponent();
        component.setDraggable(true);

        assertDoesNotThrow(() -> component.dragInitiated());
    }

    // ========== Pull-to-Refresh Tests ==========

    @Test
    void testAddPullToRefreshTask() {
        Form form = new Form(new BorderLayout());
        ScrollableComponent component = new ScrollableComponent();
        form.add(BorderLayout.CENTER, component);

        final boolean[] refreshCalled = {false};
        Runnable refreshTask = () -> refreshCalled[0] = true;

        // This sets up the refresh task
        // addPullToRefresh just stores the task, it doesn't set client properties
        assertDoesNotThrow(() -> component.addPullToRefresh(refreshTask));
    }

    @Test
    void testPullToRefreshTriggeredByPaint() {
        Form form = new Form(new BorderLayout());
        ScrollableComponent component = new ScrollableComponent();

        final boolean[] refreshCalled = {false};
        component.addPullToRefresh(() -> refreshCalled[0] = true);

        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - not needed for paint test

        // Setting up pull-to-refresh state
        component.putClientProperty("$pullToRelease", "update");

        // Paint the component which triggers paintPullToRefresh internally
        Image img = Image.createImage(100, 100, 0xFFFFFF);
        component.paintComponent(img.getGraphics());
    }

    // ========== Scroll and Motion Tests ==========

    @Test
    void testScrollableComponent() {
        ScrollableComponent component = new ScrollableComponent();

        assertTrue(component.isScrollableY());
        assertFalse(component.isScrollableX());
        assertTrue(component.isScrollable());
    }

    @Test
    void testScrollListeners() {
        ScrollableComponent component = new ScrollableComponent();

        final int[] scrollXValues = {0};
        final int[] scrollYValues = {0};

        ScrollListener listener = new ScrollListener() {
            public void scrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                scrollXValues[0] = scrollX;
                scrollYValues[0] = scrollY;
            }
        };

        // Test that we can add and remove scroll listeners without exception
        assertDoesNotThrow(() -> component.addScrollListener(listener));
        assertDoesNotThrow(() -> component.removeScrollListener(listener));

        // Note: Actually triggering scroll events via setScrollY requires EDT setup
        // (fireScrollEvent calls Display.callSerially which needs EDT)
    }

    @Test
    void testTensileDragEnabled() {
        ScrollableComponent component = new ScrollableComponent();

        // Default value depends on LAF settings (laf.isDefaultTensileDrag())
        // Just test that getter returns a boolean value
        boolean initialValue = component.isTensileDragEnabled();
        assertNotNull(initialValue);

        // Test that we can set and get the value
        component.setTensileDragEnabled(true);
        assertTrue(component.isTensileDragEnabled());

        component.setTensileDragEnabled(false);
        assertFalse(component.isTensileDragEnabled());

        // Test tensile length - default is -1
        assertEquals(-1, component.getTensileLength());
        component.setTensileLength(50);
        assertEquals(50, component.getTensileLength());
    }

    @Test
    void testScrollMotionState() {
        ScrollableComponent component = new ScrollableComponent();

        assertFalse(component.isScrollDecelerationMotionInProgress());
        assertFalse(component.isTensileMotionInProgress());
    }

    @Test
    void testGetDragSpeed() {
        ScrollableComponent component = new ScrollableComponent();

        float speed = component.getDragSpeed(false);
        assertTrue(speed >= 0, "Drag speed should be non-negative");
    }

    // ========== Style and Inline Styles Tests ==========

    @Test
    void testInlineStylesProcessing() {
        TestableComponent component = new TestableComponent();
        component.setUIID("Button");

        // Setting inline styles triggers internal processing
        component.setInlineAllStyles("color:ff0000;padding:2mm");
        assertEquals("color:ff0000;padding:2mm", component.getInlineAllStyles());

        component.setInlineSelectedStyles("color:00ff00");
        component.setInlineUnselectedStyles("color:0000ff");
        component.setInlinePressedStyles("color:ffff00");
        component.setInlineDisabledStyles("color:999999");

        // Getting styles will internally use getInline*StyleStrings methods
        Style unselected = component.getUnselectedStyle();
        Style selected = component.getSelectedStyle();
        Style pressed = component.getPressedStyle();
        Style disabled = component.getDisabledStyle();

        assertNotNull(unselected);
        assertNotNull(selected);
        assertNotNull(pressed);
        assertNotNull(disabled);
    }

    @Test
    void testStyleAnimationCreation() {
        TestableComponent component = new TestableComponent();

        Style source = component.getUnselectedStyle();
        Style target = component.getSelectedStyle();

        // This internally calls createStyleAnimation
        component.createStyleAnimation(source, target, 100, "Button");

        assertNotNull(source);
        assertNotNull(target);
    }

    // ========== Focus and Event Tests ==========

    @Test
    void testFocusLostEvent() {
        Form form = new Form();
        TestableComponent comp1 = new TestableComponent();
        TestableComponent comp2 = new TestableComponent();

        comp1.setFocusable(true);
        comp2.setFocusable(true);

        form.add(comp1);
        form.add(comp2);
        // Don't call form.show() - causes EDT issues

        // Test that we can add focus listeners without exception
        final boolean[] focusLostCalled = {false};
        FocusListener listener = new FocusListener() {
            public void focusGained(Component cmp) {}
            public void focusLost(Component cmp) {
                focusLostCalled[0] = true;
            }
        };

        assertDoesNotThrow(() -> comp1.addFocusListener(listener));
        assertDoesNotThrow(() -> comp1.removeFocusListener(listener));

        // Note: Actually triggering focus events requires form.show() which
        // needs proper Display initialization
    }

    @Test
    void testFocusLostProtectedMethod() {
        TestableComponent component = new TestableComponent();
        component.setFocusable(true);

        // focusLost is public, can call directly
        assertDoesNotThrow(() -> component.focusLost());
    }

    @Test
    void testPointerReleasedEvent() {
        // Add component to a container to ensure proper parent hierarchy
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(100);
        form.add(BorderLayout.CENTER, component);

        // Test that we can add and remove listener without exception
        final boolean[] releaseCalled = {false};
        ActionListener listener = evt -> releaseCalled[0] = true;

        assertDoesNotThrow(() -> component.addPointerReleasedListener(listener));
        assertDoesNotThrow(() -> component.removePointerReleasedListener(listener));

        // Note: Actually firing the event requires proper EDT setup,
        // so we just verify the listener management works
    }

    @Test
    void testStateChangeListener() {
        TestableComponent component = new TestableComponent();

        final boolean[] stateChanged = {false};
        ActionListener listener = evt -> stateChanged[0] = true;

        component.addStateChangeListener(listener);
        component.removeStateChangeListener(listener);

        // Just verify no exceptions
        assertNotNull(component);
    }

    @Test
    void testLongPressListener() {
        TestableComponent component = new TestableComponent();

        final boolean[] longPressCalled = {false};
        ActionListener listener = evt -> longPressCalled[0] = true;

        component.addLongPressListener(listener);
        component.removeLongPressListener(listener);

        assertNotNull(component);
    }

    @Test
    void testOnOrientationChange() {
        TestableComponent component = new TestableComponent();

        // This is public and can be called directly
        assertDoesNotThrow(() -> component.onOrientationChange());
    }

    @Test
    void testOnParentPositionChange() {
        TestableComponent component = new TestableComponent();

        // This is public and can be called directly
        assertDoesNotThrow(() -> component.onParentPositionChange());
    }

    // ========== Input Handling Tests ==========

    @Test
    void testKeyboardInput() {
        TestableComponent component = new TestableComponent();
        component.setFocusable(true);

        assertDoesNotThrow(() -> component.keyPressed(1));
        assertDoesNotThrow(() -> component.keyReleased(1));
        assertDoesNotThrow(() -> component.keyRepeated(1));
        assertDoesNotThrow(() -> component.longKeyPress(1));
    }

    @Test
    void testPointerHoverEvents() {
        TestableComponent component = new TestableComponent();

        int[] x = {10};
        int[] y = {20};

        assertDoesNotThrow(() -> component.pointerHover(x, y));
        assertDoesNotThrow(() -> component.pointerHoverPressed(x, y));
        assertDoesNotThrow(() -> component.pointerHoverReleased(x, y));
    }

    @Test
    void testPinchGesture() {
        TestableComponent component = new TestableComponent();

        assertDoesNotThrow(() -> component.pinch(1.5f));

        int[] x = {10, 20};
        int[] y = {30, 40};
        assertDoesNotThrow(() -> component.pinch(x, y));
        assertDoesNotThrow(() -> component.pinchReleased(10, 20));
    }

    // ========== Property Tests ==========

    @Test
    void testPropertyNameAccess() {
        TestableComponent component = new TestableComponent();

        // Base Component class returns null for these methods
        // They are meant to be overridden by subclasses that support properties
        String[] names = component.getPropertyNames();
        // Base implementation returns null
        assertNull(names);

        Class[] types = component.getPropertyTypes();
        assertNull(types);

        String[] typeNames = component.getPropertyTypeNames();
        assertNull(typeNames);
    }

    @Test
    void testPropertyValueAccess() {
        TestableComponent component = new TestableComponent();
        component.setName("TestComponent");

        // Base Component class returns null for getPropertyValue
        // This is meant to be overridden by subclasses
        Object value = component.getPropertyValue("name");
        assertNull(value);

        // setPropertyValue is also a stub that does nothing
        assertDoesNotThrow(() -> component.setPropertyValue("name", "NewName"));
    }

    @Test
    void testBindableProperties() {
        TestableComponent component = new TestableComponent();

        // Base Component class returns null for these methods
        // They are meant to be overridden by subclasses that support binding
        String[] bindableNames = component.getBindablePropertyNames();
        assertNull(bindableNames);

        Class[] bindableTypes = component.getBindablePropertyTypes();
        assertNull(bindableTypes);
    }

    @Test
    void testPropertyBinding() {
        TestableComponent component = new TestableComponent();
        TestBindTarget target = new TestBindTarget();

        assertDoesNotThrow(() -> component.bindProperty("name", target));
        assertDoesNotThrow(() -> component.unbindProperty("name", target));
    }

    @Test
    void testBoundPropertyValues() {
        TestableComponent component = new TestableComponent();

        // Base Component returns null for bound property values
        // This is meant to be overridden by components that support property binding
        Object value = component.getBoundPropertyValue("testProperty");
        assertNull(value);

        // setBoundPropertyValue is a no-op on base Component
        assertDoesNotThrow(() -> component.setBoundPropertyValue("testProperty", "testValue"));

        // Value should still be null after set (no-op on base Component)
        assertNull(component.getBoundPropertyValue("testProperty"));
    }

    @Test
    void testComponentState() {
        TestableComponent component = new TestableComponent();

        Object state = component.getComponentState();
        component.setComponentState(state);

        // Just verify no exceptions
        assertNotNull(component);
    }

    // ========== Utility and Miscellaneous Tests ==========

    @Test
    void testToStringAndParamString() {
        TestableComponent component = new TestableComponent();
        component.setName("MyComponent");

        String str = component.toString();
        assertNotNull(str);
        assertTrue(str.length() > 0);

        // paramString is protected, override it
        String params = component.paramString();
        assertNotNull(params);
    }

    @Test
    void testGrowShrinkAnimation() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(100);

        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - not needed for growShrink

        // Trigger grow/shrink animation
        assertDoesNotThrow(() -> component.growShrink(100));
    }

    @Test
    void testIsChildOfHierarchy() {
        Container parent = new Container();
        Container child = new Container();
        TestableComponent grandchild = new TestableComponent();

        parent.add(child);
        child.add(grandchild);

        assertTrue(grandchild.isChildOf(parent));
        assertTrue(grandchild.isChildOf(child));

        Container unrelated = new Container();
        assertFalse(grandchild.isChildOf(unrelated));
    }

    @Test
    void testHideInPortraitLandscape() {
        TestableComponent component = new TestableComponent();

        assertFalse(component.isHideInPortrait());
        assertFalse(component.isHideInLandscape());

        component.setHideInPortrait(true);
        assertTrue(component.isHideInPortrait());

        component.setHideInLandscape(true);
        assertTrue(component.isHideInLandscape());
    }

    @Test
    void testLabelForComponent() {
        TestableComponent component = new TestableComponent();
        Label label = new Label("Test Label");
        // Don't start ticker - requires EDT and Display initialization

        component.setLabelForComponent(label);
        assertSame(label, component.getLabelForComponent());

        // deinitialize should not throw even if no ticker is running
        assertDoesNotThrow(() -> component.deinitialize());
    }

    @Test
    void testGetBottomGap() {
        ScrollableComponent component = new ScrollableComponent();

        int gap = component.getBottomGap();
        assertTrue(gap >= 0);
    }

    @Test
    void testGetSelectedRect() {
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(50);
        component.setX(10);
        component.setY(20);

        Rectangle rect = component.getSelectedRect();
        assertNotNull(rect);
        assertEquals(100, rect.getWidth());
        assertEquals(50, rect.getHeight());
    }

    @Test
    void testPaintBorderBackground() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();
        component.setWidth(100);
        component.setHeight(50);

        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - not needed for paint test

        // paintBorderBackground is protected, can override
        Image canvas = Image.createImage(200, 200, 0xFFFFFF);
        Graphics g = canvas.getGraphics();

        assertDoesNotThrow(() -> component.paintBorderBackground(g));
    }

    @Test
    void testEditingInterface() {
        TestableComponent component = new TestableComponent();

        assertFalse(component.isEditing());
        assertFalse(component.isEditable());

        assertDoesNotThrow(() -> component.startEditingAsync());
        assertDoesNotThrow(() -> component.stopEditing(() -> {}));
        assertDoesNotThrow(() -> component.onEditComplete("text"));
    }

    @Test
    void testGetBaseline() {
        TestableComponent component = new TestableComponent();

        int baseline = component.getBaseline(100, 50);
        assertTrue(baseline >= -1);

        int behavior = component.getBaselineResizeBehavior();
        assertTrue(behavior >= 0);
    }

    @Test
    void testSetSameSize() {
        TestableComponent comp1 = new TestableComponent();
        TestableComponent comp2 = new TestableComponent();

        assertDoesNotThrow(() -> Component.setSameSize(comp1, comp2));
    }

    @Test
    void testCursorSupport() {
        TestableComponent component = new TestableComponent();

        boolean supported = component.isSetCursorSupported();
        assertNotNull(supported);

        int cursor = component.getCursor();
        assertTrue(cursor >= Component.DEFAULT_CURSOR);
    }

    @Test
    void testEditingDelegate() {
        TestableComponent component = new TestableComponent();

        Editable delegate = new Editable() {
            public String getEditingText() { return ""; }
            public boolean isEditing() { return false; }
            public boolean isEditable() { return true; }
            public void stopEditing(Runnable onFinish) { if (onFinish != null) onFinish.run(); }
            public void startEditingAsync() {}
            public void onEditComplete(String text) {}
        };

        component.setEditingDelegate(delegate);
        assertSame(delegate, component.getEditingDelegate());
    }

    @Test
    void testComponentRendering() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();

        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - not needed for paint test

        // Test that component can be painted
        Image img = Image.createImage(100, 100, 0xFFFFFF);
        Graphics g = img.getGraphics();

        assertDoesNotThrow(() -> component.paint(g));
    }

    @Test
    void testIsOpaque() {
        TestableComponent component = new TestableComponent();

        boolean opaque = component.isOpaque();
        assertNotNull(opaque);
    }

    @Test
    void testDragRegion() {
        TestableComponent component = new TestableComponent();
        component.setDraggable(true);
        component.setX(0);
        component.setY(0);
        component.setWidth(100);
        component.setHeight(100);

        boolean isDragRegion = component.isDragRegion(50, 50);
        assertNotNull(isDragRegion);
    }

    @Test
    void testFindSurface() {
        Form form = new Form(new BorderLayout());
        Container surface = new Container();
        surface.setSurface(true);

        TestableComponent component = new TestableComponent();
        surface.add(component);
        form.add(BorderLayout.CENTER, surface);
        // Don't call form.show() - not needed for findSurface

        Container foundSurface = component.findSurface();
        assertNotNull(foundSurface);
    }

    @Test
    void testDraggedCoordinates() {
        TestableComponent component = new TestableComponent();
        component.setDraggable(true);

        int dragX = component.getDraggedx();
        int dragY = component.getDraggedy();

        assertEquals(0, dragX);
        assertEquals(0, dragY);
    }

    @Test
    void testHandlesInput() {
        TestableComponent component = new TestableComponent();

        boolean handles = component.handlesInput();
        assertFalse(handles);
    }

    @Test
    void testGridPosition() {
        TestableComponent component = new TestableComponent();

        int gridX = component.getGridPosX();
        int gridY = component.getGridPosY();

        assertTrue(gridX >= 0);
        assertTrue(gridY >= 0);
    }

    @Test
    void testTactileTouch() {
        TestableComponent component = new TestableComponent();

        // tactileTouch defaults to isFocusable(), so set focusable to false first
        component.setFocusable(false);
        component.setTactileTouch(false);

        assertFalse(component.isTactileTouch(10, 20));
        assertFalse(component.isTactileTouch());

        component.setTactileTouch(true);
        assertTrue(component.isTactileTouch());
    }

    @Test
    void testSideSwipeBlocking() {
        TestableComponent component = new TestableComponent();

        assertFalse(component.blocksSideSwipe());
        assertFalse(component.shouldBlockSideSwipe());
        assertFalse(component.shouldBlockSideSwipeLeft());
        assertFalse(component.shouldBlockSideSwipeRight());
    }

    @Test
    void testGrabsPointerEvents() {
        TestableComponent component = new TestableComponent();

        assertFalse(component.isGrabsPointerEvents());
    }

    @Test
    void testShouldRenderComponentSelection() {
        TestableComponent component = new TestableComponent();

        boolean shouldRender = component.shouldRenderComponentSelection();
        assertNotNull(shouldRender);
    }

    @Test
    void testStickyDrag() {
        TestableComponent component = new TestableComponent();

        assertFalse(component.isStickyDrag());
    }

    @Test
    void testHintLabel() {
        TestableComponent component = new TestableComponent();

        // Base Component class has stub implementations that always return null
        assertNull(component.getHintLabelImpl());

        Label hint = new Label("Hint");
        // setHintLabelImpl is a stub in base Component - meant to be overridden
        assertDoesNotThrow(() -> component.setHintLabelImpl(hint));

        // shouldShowHint returns false in base Component
        assertFalse(component.shouldShowHint());
    }

    @Test
    void testResetFocusable() {
        TestableComponent component = new TestableComponent();
        component.setFocusable(true);

        assertDoesNotThrow(() -> component.resetFocusable());
    }

    @Test
    void testRefreshTheme() {
        TestableComponent component = new TestableComponent();

        assertDoesNotThrow(() -> component.refreshTheme());
    }

    @Test
    void testInnerPreferredSize() {
        TestableComponent component = new TestableComponent();
        component.setPreferredW(100);
        component.setPreferredH(50);
        component.getAllStyles().setPadding(5, 5, 5, 5);

        int innerW = component.getInnerPreferredW();
        int innerH = component.getInnerPreferredH();

        assertTrue(innerW <= 100);
        assertTrue(innerH <= 50);
    }

    @Test
    void testIsVisibleOnForm() {
        Form form = new Form(new BorderLayout());
        TestableComponent component = new TestableComponent();

        form.add(BorderLayout.CENTER, component);
        // Don't call form.show() - just test that the method works

        // Without form being shown, component won't be visible on form
        boolean visible = component.isVisibleOnForm();
        // Just verify the method doesn't throw
        assertNotNull(visible);
    }

    // ========== Helper Classes ==========

    private static class TestableComponent extends Component {
        boolean painted = false;
        boolean dragFinishedCalled = false;

        TestableComponent() {
            setUIID("Label");
        }

        @Override
        public void paint(Graphics g) {
            painted = true;
            super.paint(g);
        }

        @Override
        protected void dragFinished(int x, int y) {
            dragFinishedCalled = true;
            super.dragFinished(x, y);
        }

        @Override
        protected String paramString() {
            return super.paramString();
        }

        @Override
        protected void paintBorderBackground(Graphics g) {
            super.paintBorderBackground(g);
        }
    }

    private static class ScrollableComponent extends Component {
        ScrollableComponent() {
            setUIID("Label");
        }

        @Override
        public boolean isScrollableY() {
            return true;
        }

        @Override
        protected Dimension calcScrollSize() {
            return new Dimension(getWidth(), getHeight() * 2);
        }
    }

    private static class TestBindTarget implements BindTarget {
        private Object value;

        public void set(Object value) {
            this.value = value;
        }

        public Object get() {
            return value;
        }

        public void propertyChanged(Component source, String property, Object oldValue, Object newValue) {
            this.value = newValue;
        }
    }
}
